package com.bantang.template;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ArrayUtil;
import com.bantang.model.JudgeInfo;
import com.bantang.model.SandBoxParam;
import com.bantang.model.SandBoxVO;
import com.bantang.utils.ExecResult;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.StatsCmd;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;

import java.io.Closeable;
import java.io.IOException;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * docker 实现代码沙箱
 */
public class DockerSandBoxTemplate extends CodeSandBoxTemplate {

    private boolean judge = true;

    @Override
    public SandBoxVO runCode(SandBoxParam sandBoxParam) {
        List<String> input = sandBoxParam.getInput();

        //-------------------------docker 实现代码沙箱-----------------------

        //1.创建docker客户端
        DockerClient dockerClient = DockerClientBuilder.getInstance().build();

        //1.1如果是第一次初始化 拉取java环境 镜像
        String image = "openjdk:8-alpine";//alpine 是轻量版本
        if (judge) {
            //拉取镜像
            dockerClient.pullImageCmd(image);
            judge = false;
        }

        //1.2 创建容器cmd 命令预设
        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image);

        //1.3 通过HostConfig配置 限制最大内存 并把java编译好的文件夹映射到docker根目录/app 下
        HostConfig hostConfig = new HostConfig();
        //把本地路径映射到docker/app下
        System.out.println("我是映射的路径");
        System.out.println(userCodePath);
        hostConfig.setBinds(new Bind(userCodePath, new Volume("/app")));
        //限制最大内存为100m
        hostConfig.withMemory(100 * 1000 * 1000L);
        //hostConfig.withSecurityOpts(Arrays.asList("secomp=安全管理配置"));

        //2 绑定容器配置
        CreateContainerResponse createContainerResponse = containerCmd
                .withHostConfig(hostConfig)//设置host配置
                .withNetworkDisabled(true)//限制网络
                .withReadonlyRootfs(true)//限制不能向根目录写文件
                .withAttachStdin(true)//withAttach 为了得到控制台输出 以防万一
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withTty(true)
                .exec();

        //3 获取容器id
        String containerId = createContainerResponse.getId();
        System.out.println("容器id" + containerId);
        //4 启动容器
        dockerClient.startContainerCmd(containerId).exec();

        //-------------------------启动容器 需要多次吗？
        //todo 这里放静态块里面 只执行一次 拉镜像和启动容器 包括配置限制和映射 但是映射路径 和 containerId 如何处理？

        long time = 0L;
        //docker执行java程序cmd： docker exec keen_blackwell java -cp /app Solve 1 3
        System.out.println("docker执行java程序cmd： docker exec dreamy_tesla java -cp /app Solve 1 3");

        //5 init 包装返回VO
        ExecResult runResult = new ExecResult();
        JudgeInfo judgeInfo = new JudgeInfo();
        List<String> outputValues = new ArrayList<>();
        Long maxTime = 0L;

        //6 多次执行 input 获取output 这里在已经运行的容器里/bin/bash里执行
        for (String inputArray : input) {

            StopWatch stopWatch = new StopWatch();
            String[] args = inputArray.split(" ");
            String[] cmdArray = ArrayUtil.append(new String[]{"docker", "exec", "java", "-cp", "/app", "Solve"}, args);

            System.out.println("执行命令");
            System.out.println(Arrays.toString(cmdArray));
            //1 配置运行java代码 前置条件 这里创建exec 哪个控制台？
            ExecCreateCmdResponse exec = dockerClient.execCreateCmd(containerId)
                    .withCmd(cmdArray) //设置命令行
                    .withAttachStdin(true) //withAttach 为了得到控制台输出 以防万一
                    .withAttachStderr(true)
                    .withAttachStdout(true)
                    .exec();
            //获取exec的 id
            String execId = exec.getId();

            //限制是否超时 默认是超时
            final boolean[] isComplete = {true};

            //2 配置执行回调函数
            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {

                //运行完成 改变超时判断 如果在指定时间运行不完 状态不会改变
                @Override
                public void onComplete() {
                    isComplete[0] = false;
                    super.onComplete();
                }

                //获取输出结果
                @Override
                public void onNext(Frame frame) {

                    StreamType type = frame.getStreamType();
                    if (StreamType.STDERR.equals(type)) {
                        System.out.println("错误输出结果:" + new String(frame.getPayload()));
                        runResult.setMessage(new String(frame.getPayload()));
                    } else {
                        System.out.println("输出结果:" + new String(frame.getPayload()));
                        outputValues.add(new String(frame.getPayload()));
//                            runResult.setResult(new String(frame.getPayload()));
                    }
                    super.onNext(frame);
                }
            };


            try {
                stopWatch.start();
                //3 开始执行代码
                System.out.println("开始执行代码");
                dockerClient.execStartCmd(execId)
                        .exec(execStartResultCallback)
                        .awaitCompletion(5000L, TimeUnit.MICROSECONDS);//控制超时 等待5秒执行下一步

                if (isComplete[0]) {
                    //超时
                    judgeInfo.setMessage("运行超时");
                    return sandBoxVO;
                }
                stopWatch.stop();
                time = stopWatch.getLastTaskTimeMillis();

            } catch (InterruptedException e) {
                judgeInfo.setMessage("系统错误");
                return sandBoxVO;
            }

            //运行后 设置时间
            maxTime = maxTime < time ? time : maxTime;

        }


        //最大内存值
        final long[] maxMemory = {0L};
        //获取占用内存
        StatsCmd statsCmd = dockerClient.statsCmd(containerId);
        ResultCallback<Statistics> exec = statsCmd.exec(new ResultCallback<Statistics>() {
            @Override
            public void onNext(Statistics statistics) {
                System.out.println("内存占用" + statistics.getMemoryStats().getUsage());
                maxMemory[0] = Math.max(statistics.getMemoryStats().getUsage(), maxMemory[0]);
                //获取占用内存
                judgeInfo.setMemory(maxMemory[0]);
            }

            @Override
            public void onStart(Closeable closeable) {
            }

            @Override
            public void onError(Throwable throwable) {
            }

            @Override
            public void onComplete() {
            }

            @Override
            public void close() throws IOException {
            }
        });
//            exec.close();


        sandBoxVO.setOutput(outputValues);
        judgeInfo.setTime(maxTime);

        sandBoxVO.setJudgeInfo(judgeInfo);

        System.out.println("sandBoxVo");
        System.out.println(sandBoxVO);
        return sandBoxVO;
    }
}
